package com.example.mwavmedia.audio;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.text.TextUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

public class PcmEncoder {
    private EncodeThread encodeThread;

    public void start(String path){
        stop();
        encodeThread = new EncodeThread(path,
                MediaRecorder.AudioSource.MIC,
                44100,
                AudioFormat.CHANNEL_IN_STEREO,
                AudioFormat.ENCODING_PCM_16BIT,
                96000,
                4096,
                MediaFormat.MIMETYPE_AUDIO_AAC);
        encodeThread.start();
    }
    public void stop(){
        if (encodeThread == null) return;
        encodeThread.stopEncode();
        encodeThread = null;
    }

    private static class EncodeThread extends Thread{
        private static final long TIMEOUT_MS = 2000L;
        private AudioRecord audioRecord;
        private MediaCodec mediaCodec;

        private FileOutputStream fileOutputStream;

        private final String path;
        private final int audioSource;
        private final int sampleRateInHz;
        private final int channelConfig;
        private final int audioFormat;
        private final int bitRate;
        private final int maxInputSize;
        private final String mine;

        private int channelCount;
        private int bufferSizeInBytes;
        private boolean isStopEncode = false;

        public EncodeThread(String path,
                            int audioSource,
                            int sampleRateInHz,
                            int channelConfig,
                            int audioFormat,
                            int bitRate,
                            int maxInputSize,
                            String mine){
            this.path = path;
            this.audioSource = audioSource;
            this.sampleRateInHz = sampleRateInHz;
            this.channelConfig = channelConfig;
            this.audioFormat = audioFormat;
            this.bitRate = bitRate;
            this.maxInputSize = maxInputSize;
            this.mine = mine;
        }

        @Override
        public void run() {
            super.run();
            initIO();
            initAudioRecord();
            initMediaCodec();
            encode();
        }
        private void initIO(){
            if (TextUtils.isEmpty(path)){
                return;
            }
            File file = new File(path);
            if (file.exists()){
                file.delete();
            }
            try {
                fileOutputStream = new FileOutputStream(path);
            }catch (FileNotFoundException e){
                e.printStackTrace();
                fileOutputStream = null;
            }
        }
        private void initAudioRecord(){
            bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);
            try {
                audioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,bufferSizeInBytes);
            }catch (SecurityException securityException){
                securityException.printStackTrace();
                securityException = null;
            }
        }
        private void initMediaCodec(){
            channelCount = 1;
            if (channelConfig == AudioFormat.CHANNEL_IN_MONO){
                channelCount = 1;
            }else if (channelConfig == AudioFormat.CHANNEL_IN_STEREO){
                channelCount = 2;
            }
            MediaFormat format = MediaFormat.createAudioFormat(mine,sampleRateInHz,channelCount);
            format.setInteger(MediaFormat.KEY_BIT_RATE,bitRate);
            format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE,maxInputSize);
            try {
                mediaCodec = MediaCodec.createEncoderByType(mine);
                mediaCodec.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
            }catch (IOException ioException){
                ioException.printStackTrace();
                ioException = null;
            }
        }
        private void encode(){
            if (audioRecord == null || fileOutputStream == null || mediaCodec == null) return;
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            audioRecord.startRecording();
            mediaCodec.start();
            for (;;){
                if (isStopEncode){
                    release();
                    break;
                }

                ///////////////input
                int inputBufferId = mediaCodec.dequeueInputBuffer(TIMEOUT_MS);
                if (inputBufferId >= 0){
                    ByteBuffer byteBuffer = mediaCodec.getInputBuffer(inputBufferId);
                    int readSize = -1;
                    if (byteBuffer != null){
                        readSize = audioRecord.read(byteBuffer,bufferSizeInBytes);
                    }
                    if (readSize <= 0){
                        mediaCodec.queueInputBuffer(inputBufferId,0,0,0,
                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        isStopEncode = true;
                    }else {
                        mediaCodec.queueInputBuffer(inputBufferId,0,readSize,
                                System.nanoTime()/1000,0);
                    }
                }
                ///////////////output
                int outBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo,TIMEOUT_MS);
                if (outBufferId >= 0){
                    ByteBuffer byteBuffer = mediaCodec.getOutputBuffer(outBufferId);
                    int size = bufferInfo.size;
                    if (byteBuffer != null && size > 0){
                        byte[] data = new byte[size + 7];
                        addADTSHeader(data,size+7);
                        byteBuffer.get(data,7,size);
                        byteBuffer.clear();
                        try {
                            fileOutputStream.write(data);
                        }catch (IOException exception){
                            exception.printStackTrace();
                        }
                        mediaCodec.releaseOutputBuffer(outBufferId,false);
                    }
                }

            }
        }
        void stopEncode(){
            isStopEncode = true;
            try {
                join(2000);
            }catch (InterruptedException interruptedException){
                interruptedException.printStackTrace();
            }
        }
        private void addADTSHeader(byte[] packet, int packetLen) {
            int profile = 2; // AAC
            int freqIdx = 4; // 44.1kHz
            packet[0] = (byte) 0xFF;
            packet[1] = (byte) 0xF9;
            packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (channelCount >> 2));
            packet[3] = (byte) (((channelCount & 3) << 6) + (packetLen >> 11));
            packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
            packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
            packet[6] = (byte) 0xFC;
        }
        private void release(){
            if (fileOutputStream != null){
                try {
                    fileOutputStream.close();
                    fileOutputStream = null;
                }catch (IOException ioException){
                    ioException.printStackTrace();
                }
            }
            if (audioRecord != null){
                audioRecord.stop();
                audioRecord.release();
                audioRecord = null;
            }
            if (mediaCodec != null){
                mediaCodec.release();
                mediaCodec = null;
            }
        }
    }
}
